package com.gitee.simpleplus;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.math.BigDecimal;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLXML;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

import static org.apache.commons.lang3.StringUtils.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONReader;
import com.beust.jcommander.Parameter;
import com.gitee.simpleplus.db.Column;
import com.gitee.simpleplus.db.Table;

import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
public class Main {
	private static Logger logger = LoggerFactory.getLogger(Main.class);
	
	private static FileFilter ff;
	
	private static Configuration cnf;
	    
    static{
    	 cnf = new Configuration(Configuration.VERSION_2_3_23);
    	 cnf.setObjectWrapper(new DefaultObjectWrapper(Configuration.VERSION_2_3_23));
    }
    static String sourceProjectClassPath = (System.getProperty("user.dir")+"/target/classes/").replace("/", File.separator);
    
    @Parameter(names = { "--f", "-file" }, description = "配置文件的路径")
    private String externalJsonFilePath;
    
	public static void main(String[] args) {
		Main m = new Main();
		try (Conf conf = readConf(m.externalJsonFilePath)){
			ff = conf.findFileFilter();
			
			if(!isBlank(conf.getFixed())){
				genFixedFiles(conf);
			}

			if(!isBlank(conf.getCust())){
				genCustFiles(conf);
			}
		} catch (Exception e) {
			logger.error("",e);
		}
	}

	private static void genCustFiles(Conf conf) throws Exception {
		String tmp_fixed_dir = sourceProjectClassPath + conf.getCust();
		conf.getTables().stream().forEach(t->{
			
			getListFiles(new File(tmp_fixed_dir)).stream().forEach(tmp ->{
//				logger.info(t.getAbsolutePath());
				String templateFileName = tmp.getName();
				String targetFileName = templateFileName.substring(0, templateFileName.indexOf(".ftl")).replace("${model}", t.getUpperFirstLetterName());
				String templateFileRelativeDir = tmp.getAbsolutePath().replace(sourceProjectClassPath,File.separator).replace(tmp.getName(),"");
				//logger.info(templateFileRelativeDir);
//				logger.info(t.getAbsolutePath().replace(tmp_fixed_dir,"").replace("$groupId", conf.getGroupId()).replaceAll("\\.", "/").replace("/", File.separator));
				String targetFileAbsoluteDir = conf.getTargetFileAbsoluteDir() + tmp.getAbsolutePath().replace(tmp_fixed_dir,"").replace(tmp.getName(),"").replace("$groupId", conf.getGroupId()).replaceAll("\\.", "/").replace("/", File.separator);
				//logger.info(templateFileRelativeDir);
				t.setGroupId(conf.getGroupId());
				gen(t, 
						templateFileRelativeDir,
						templateFileName,
						targetFileAbsoluteDir, 
						targetFileName);
				});
		});
		//conn.prepareStatement("");
	}
	
	public static final String upperFirst(String s) {
		if(s==null)
			return "";
		int len = s.length();
		if (len <= 0)
			return "";
		
		StringBuffer sb = new StringBuffer();
		Arrays.stream(split(s, "_")).forEach(tmp->sb.append(upperCase(tmp.substring(0, 1))).append(tmp.substring(1)));
		return sb.toString();
	}

	public static final String lowerFirst(String s) {
		int len = s.length();
		if (len <= 0)
			return "";
		StringBuffer sb = new StringBuffer();
		Arrays.stream(split(s, "_")).forEach(tmp->sb.append(upperCase(tmp.substring(0, 1))).append(tmp.substring(1)));
		return lowerCase(sb.substring(0,1)) + sb.substring(1);
	}

	
	public static String getType(ResultSetMetaData rsmd, int i) throws SQLException {
		int count = rsmd.getColumnCount();
		if (i > count)
			return "";

		int columnType = rsmd.getColumnType(i);
		switch (columnType) {
		case java.sql.Types.ARRAY:
			return Array.class.getSimpleName();
		case java.sql.Types.BIGINT:
			return Long.class.getSimpleName();
		case java.sql.Types.BINARY:
			return "byte[]";
		case java.sql.Types.BIT:
			return Boolean.class.getSimpleName();
		case java.sql.Types.BLOB:
			return Blob.class.getName();
		case java.sql.Types.BOOLEAN:
			return Boolean.class.getSimpleName();
		case java.sql.Types.CHAR:
			return String.class.getSimpleName();
		case java.sql.Types.CLOB:
			return Clob.class.getName();
		case java.sql.Types.DATE:
			return java.util.Date.class.getName();
		case java.sql.Types.DECIMAL:
			return BigDecimal.class.getName();
		case java.sql.Types.DISTINCT:
			break;
		case java.sql.Types.DOUBLE:
			return Double.class.getSimpleName();
		case java.sql.Types.FLOAT:
			return Float.class.getSimpleName();
		case java.sql.Types.INTEGER:
			return Integer.class.getSimpleName();
		case java.sql.Types.JAVA_OBJECT:
			return Object.class.getSimpleName();
		case java.sql.Types.LONGVARCHAR:
			return String.class.getSimpleName();
		case java.sql.Types.LONGNVARCHAR:
			return String.class.getSimpleName();
		case java.sql.Types.LONGVARBINARY:
			return "byte[]";
		case java.sql.Types.NCHAR:
			return String.class.getName();
		case java.sql.Types.NCLOB:
			return NClob.class.getName();
		case java.sql.Types.NULL:
			break;
		case java.sql.Types.NUMERIC:
			return BigDecimal.class.getName();
		case java.sql.Types.NVARCHAR:
			return String.class.getSimpleName();
		case java.sql.Types.OTHER:
			return Object.class.getSimpleName();
		case java.sql.Types.REAL:
			return Double.class.getSimpleName();
		case java.sql.Types.REF:
			break;
		case java.sql.Types.ROWID:
			return RowId.class.getName();
		case java.sql.Types.SMALLINT:
			return Short.class.getSimpleName();
		case java.sql.Types.SQLXML:
			return SQLXML.class.getName();
		case java.sql.Types.STRUCT:
			break;
		case java.sql.Types.TIME:
			return Time.class.getName();
		case java.sql.Types.TIMESTAMP:
			return java.util.Date.class.getName();
		case java.sql.Types.TINYINT:
			return Byte.class.getSimpleName();
		case java.sql.Types.VARBINARY:
			return "byte[]";
		case java.sql.Types.VARCHAR:
			return String.class.getSimpleName();
		default:
			break;
		}
		return "";
	}
	
	public static String getBasicType(String type){
		if (type.equals("Boolean"))
			type = "boolean";
		if (type.equals("Byte") || type.equals("java.lang.Byte"))
			type = "byte";
		else if (type.equals("Short") || type.equals("java.lang.Short"))
			type = "short";
		else if (type.equals("Integer") || type.equals("java.lang.Integer"))
			type = "int";
		else if (type.equals("Long") || type.equals("java.lang.Long"))
			type = "long";
		else if (type.equals("Float") || type.equals("java.lang.Float"))
			type = "float";
		else if (type.equals("Double") || type.equals("java.lang.Double"))
			type = "double";

		return type;
	}

	private static List<String> findTables(Connection conn) throws SQLException {
		DatabaseMetaData dme = conn.getMetaData();
		List<ResultSet> sets = new ArrayList<ResultSet>();
		sets.add(dme.getTables("", "", "", new String[] { "TABLE" }));
		
		List<String> tables = new ArrayList<>();
		
		sets.stream().forEach(r ->{
			try {
				while(r.next()){
					ResultSetMetaData metaData = r.getMetaData();
					int clos = metaData.getColumnCount();
					for (int i = 0; i < clos; i++) {
						if("TABLE_NAME".equals(metaData.getColumnName(i + 1))){
							tables.add((String) r.getObject(i + 1));
						}
					}
				}
				r.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			
		});
		return tables;
	}

	private static void genFixedFiles(Conf conf) {
		String tmp_fixed_dir = sourceProjectClassPath + conf.getFixed();
		
		getListFiles(new File(tmp_fixed_dir)).stream().forEach(t ->{
//			logger.info(t.getAbsolutePath());
			String templateFileName = t.getName();
			String targetFileName = templateFileName.substring(0, templateFileName.indexOf(".ftl"));
			String templateFileRelativeDir = t.getAbsolutePath().replace(sourceProjectClassPath,File.separator).replace(t.getName(),"");
			//logger.info(templateFileRelativeDir);
//			logger.info(t.getAbsolutePath().replace(tmp_fixed_dir,"").replace("$groupId", conf.getGroupId()).replaceAll("\\.", "/").replace("/", File.separator));
			String targetFileAbsoluteDir = conf.getTargetFileAbsoluteDir() + t.getAbsolutePath().replace(tmp_fixed_dir,"").replace(t.getName(),"").replace("$groupId", conf.getGroupId()).replaceAll("\\.", "/").replace("/", File.separator);
			//logger.info(templateFileRelativeDir);
			
			gen(conf,
					templateFileRelativeDir,
					templateFileName,
					targetFileAbsoluteDir, 
					targetFileName.equals("b.gitignore") ? targetFileName.substring(1) : targetFileName);
			});
	}
	
	
	private static void gen(Object templateData, String templateFileRelativeDir, String templateFileName, String targetFileAbsoluteDir, String targetFileName){
        try{
			cnf.setClassForTemplateLoading(Main.class, templateFileRelativeDir);
            Template template = cnf.getTemplate(templateFileName);
            template.setLocale(Locale.CHINA);
            if(!targetFileAbsoluteDir.endsWith(File.separator))
				targetFileAbsoluteDir += File.separator;
            mkdir(targetFileAbsoluteDir);
            File file = new File(targetFileAbsoluteDir + targetFileName);
            if(file.exists() && file.renameTo(new File(file.getAbsolutePath()+".oldbak"))){
            }
			try(Writer fw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file),"UTF-8"))){
            	template.process(templateData, fw);
            	logger.info(targetFileAbsoluteDir + targetFileName);
            }
        }catch (Throwable e){
            logger.error("Can not found the template file, path is \"" + templateFileRelativeDir + templateFileName);
            e.printStackTrace();
            throw new RuntimeException("IOException occur , please check !! ");
        }
    }

	 private static void mkdir(String targetFileAbsoluteDir) {
		File f = new File(targetFileAbsoluteDir);
		if (!f.exists() && f.mkdirs());
	}

	public static ArrayList<File> getListFiles(File file) {
        ArrayList<File> files = new ArrayList<File>();
        if (file.isFile() && ff.accept(file)) {
            files.add(file);
            return files;
        } else if (file.isDirectory()) {
			Arrays.stream(file.listFiles()).forEach(tmp -> files.addAll(getListFiles(tmp)));
        }
        return files;
    }
	 
	private static Conf readConf(String externalJsonFilePath) throws Exception{
		try(JSONReader r = new JSONReader(isBlank(externalJsonFilePath) ? new InputStreamReader(ClassLoader.getSystemResourceAsStream("conf.json"), "UTF-8") :new InputStreamReader(new FileInputStream(externalJsonFilePath)))){
			Conf conf = JSON.parseObject(r.readString(), Conf.class);
			init(conf);
			return conf;
		}
	}

	private static void init(Conf conf) throws Exception{
		Connection conn = conf.get();
		List<String> tableNames = findTables(conn);
		logger.info("{}", tableNames.toString());
		List<Table> tables = new ArrayList<>();
		tableNames.stream().forEach(t ->{
			try {
				
				Table ts = new Table();
				ts.setName(t);
				ts.setLowerFirstLetterName(lowerFirst(t));
				ts.setUpperFirstLetterName(upperFirst(t));
				
				
				ResultSet rs0 = conn.getMetaData().getPrimaryKeys(null, "%", t);
				String primaryKey = null;
				 while (rs0.next()) {
	                 primaryKey = rs0.getString("COLUMN_NAME");
	                 ts.setPrimaryKey(primaryKey);
	                 ts.setUpperFirstLetterPrimaryKey(upperFirst(primaryKey));
	                 ts.setLowerFirstLetterPrimaryKey(lowerFirst(primaryKey));
	                 break;
				 }
				 
				 String stringCarrayNames1 = "";
		            String stringCarrayNames2 = "";
		            String stringCarrayNames3 = "";
		            String stringCarrayNames4 = "";
		            String stringCarrayNames5 = "";

		            String stringCarrayNames6 = "";
		            String stringCarrayNames7 = "";


				 
				PreparedStatement prepareStatement = conn.prepareStatement("select * from " + t +" limit 0 , 1");
				ResultSet rs = prepareStatement.executeQuery();
				ResultSetMetaData metaData = rs.getMetaData();
				int len = metaData.getColumnCount();
				
				for (int j = 0; j < len; j++) {
					Column column = new Column();
					column.setName(metaData.getColumnName(j + 1));
					column.setLowerFirstLetterName(lowerFirst(column.getName()));
					column.setUpperFirstLetterName(upperFirst(column.getName()));
					column.setType(getType(metaData, j + 1));
					if(column.getName().equals(primaryKey)){
						ts.setPrimaryKeyType(getType(metaData, j + 1));
						column.setPrimaryKey(true);
					}
					column.setAutoIncremet(metaData.isAutoIncrement(j+1));
					column.setIndex(j);
					ts.getColumns().add(column);
					
					
					if (!"".endsWith(stringCarrayNames1)) {
	                    stringCarrayNames1 += ", ";
	                }
	                if (!"".endsWith(stringCarrayNames2)) {
	                    stringCarrayNames2 += ", ";
	                }
	                if (!"".endsWith(stringCarrayNames3)) {
	                    stringCarrayNames3 += ", ";
	                }
	                if (!"".endsWith(stringCarrayNames4)) {
	                    stringCarrayNames4 += ", ";
	                }
	                if (!"".endsWith(stringCarrayNames5)) {
	                    stringCarrayNames5 += ", ";
	                }
	                //
	                stringCarrayNames1 += column.getLowerFirstLetterName();

	                stringCarrayNames2 += column.getType() + " " + column.getLowerFirstLetterName();

	                if(!column.isAutoIncremet()) { //如果是主键，不需要在 insert 语句中 (Mabatis)
	                    stringCarrayNames3 += column.getName();
	                }
	                if(!column.isAutoIncremet()) { //如果是主键，不需要在 insert 语句中 (Mabatis)
	                    stringCarrayNames4 += String.format("#{%s}", column.getLowerFirstLetterName());
	                }
	                if(!column.isAutoIncremet()) { //如果是主键，不需要在 insert 语句中 (Mabatis)
	                    stringCarrayNames5 += String.format("%s=#{%s}", column.getName(),
	                            column.getName());
	                }

	                if (!column.getName().equals("ID") && !column.getName().equals("id")) {

	                    if (!"".endsWith(stringCarrayNames6)) {
	                        stringCarrayNames6 += ", ";
	                    }

	                    if (!"".endsWith(stringCarrayNames7)) {
	                        stringCarrayNames7 += ", ";
	                    }
	                    stringCarrayNames6 += column.getName();

	                    stringCarrayNames7 += String.format("#{%s}", column.getLowerFirstLetterName());
	                }
	                
	                ts.setStringCarrayNames1(stringCarrayNames1);
	                ts.setStringCarrayNames2(stringCarrayNames2);
	                ts.setStringCarrayNames3(stringCarrayNames3);
	                ts.setStringCarrayNames4(stringCarrayNames4);
	                ts.setStringCarrayNames5(stringCarrayNames5);
	                ts.setStringCarrayNames6(stringCarrayNames6);
	                ts.setStringCarrayNames7(stringCarrayNames7);
				}
				tables.add(ts);
				rs.close();
				prepareStatement.close();
				
				conf.setTables(tables);
			} catch (SQLException e) {
				e.printStackTrace();
			}
		});
	}
}
